﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Pivot;
using System.Collections.Generic;
using System.Windows.Interactivity;
using Microsoft.Expression.Interactivity.Core;

namespace PivotPhysics_Viewer
{
    /// <summary>
    /// Track navagation and filter changes in a PivotViewer Control. Expose ICommands
    /// that buttons (and the like) can use to cause it to navagate back and forth.
    /// </summary>
    public class PivotNavagationTracker
    {
        PivotViewer _viewer;

        public PivotNavagationTracker(PivotViewer viewer)
        {
            _viewer = viewer;
            _viewer.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(AssociatedObject_PropertyChanged);
            SetupActions();
        }

        #region BehaviorConfiguration

        /// <summary>
        /// Property has changed - see if it is one we care about.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AssociatedObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CollectionUri" || e.PropertyName == "ViewerState")
            {
                PivotViewChangedLinearlize();
            }
        }

        void AssociatedObject_CollectionLoadingCompleted(object sender, EventArgs e)
        {
            PivotViewChangedLinearlize();
        }

        #endregion

        #region Navagation Stack

        /// <summary>
        /// a locatoin in a user's journey. :-)
        /// </summary>
        class NavagationLocatoin
        {
            public Uri _uri;
            public string _viewState;

            /// <summary>
            /// Do a deep compare.
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj)
            {
                NavagationLocatoin other = obj as NavagationLocatoin;
                if (other == null)
                {
                    return false;
                }
                return other._uri == _uri && other._viewState == _viewState;
            }

            public override int GetHashCode()
            {
                return _viewState.GetHashCode() + _uri.GetHashCode();
            }
        }

        /// <summary>
        /// A huge stack of where we are.
        /// </summary>
        List<NavagationLocatoin> _locationList = new List<NavagationLocatoin>();

        /// <summary>
        /// Where we are in the location array above.
        /// </summary>
        private int _locationIndex = 0;

        private bool _pivotViewerUpdateInProgress = false;

        /// <summary>
        /// The ViewerState can change twice in response to one "click" from the user on a filter guy. The good news is these guys come
        /// in while the UI thread is being used to generate these updates. So by forcing our updates to the navagation stack updates
        /// to go through the UI thread (it is quick) we can make sure that we only respond to the proper updates.
        /// </summary>
        private void PivotViewChangedLinearlize()
        {
            if (!_pivotViewerUpdateInProgress)
            {
                _pivotViewerUpdateInProgress = true;
                _viewer.Dispatcher.BeginInvoke(() =>
                {
                    PivotViewChanged();
                    _pivotViewerUpdateInProgress = false;
                });
            }
        }

        /// <summary>
        /// The view has changed. Now we have to figure out what is going on. The Pivot Control has a particularly nasty habit
        /// of sending multiple property changed guys for a single action by the user. So we need to capture timing info and hope
        /// we get it right.
        /// </summary>
        private void PivotViewChanged()
        {            
            ///
            /// Figure out the current state. When the collection changes, but VS is null,
            /// that usually means a null startup - ignore it, as the actual VS will be
            /// showing up shortly as soon as the collection has been fully loaded.
            /// 

            NavagationLocatoin nl = new NavagationLocatoin();
            nl._uri = _viewer.CollectionUri;
            nl._viewState = _viewer.ViewerState;

            if (nl._viewState == null || nl._uri == null)
            {
                return;
            }

            ///
            /// Special case if we have no trail at all!
            /// 

            if (_locationList.Count == 0)
            {
                _locationIndex = 0;
                _locationList.Add(nl);
                return;
            }

            ///
            /// If the user did nothing, well, then we do nothing.
            /// 

            if (_locationList[_locationIndex].Equals(nl))
            {
                return;
            }

            ///
            /// Are we moving backwards?
            /// 

            if (_locationIndex > 0)
            {
                if (_locationList[_locationIndex - 1].Equals(nl))
                {
                    _locationIndex = _locationIndex - 1;
                    UpdateActionStatus();
                    return;
                }
            }

            ///
            /// Are we moving forwards?
            /// 

            if (_locationList.Count > (_locationIndex+1))
            {
                if (_locationList[_locationIndex + 1].Equals(nl))
                {
                    _locationIndex = _locationIndex + 1;
                    UpdateActionStatus();
                    return;
                }
            }

            ///
            /// Ok - we are doing something totally new. This means we are going down another branch in the path,
            /// so if there is anything in the forward direction, well, then we have to move there. :-)
            /// 

            while (_locationList.Count > (_locationIndex + 1))
            {
                _locationList.RemoveAt(_locationList.Count-1);
            }

            _locationList.Add(nl);
            _locationIndex = _locationIndex + 1;

            ///
            /// Since this is cheap - just tell everyone to update the status
            /// 

            UpdateActionStatus();
        }

        #endregion

        #region NavagationCommands

        /// <summary>
        /// Run this when you want the browser to move forward one in the stack.
        /// </summary>
        private CommandOnStatic _moveForwardCommand;
        public ICommand MoveForwardCommand { get { return _moveForwardCommand; } }

        /// <summary>
        /// Run this when you want the browser to move back one in the stack.
        /// </summary>
        public ICommand MoveBackCommand { get { return _moveBackCommand; } }
        private CommandOnStatic _moveBackCommand;

        /// <summary>
        /// Helper class to deal with commands that we are going to be handing out...
        /// </summary>
        class CommandOnStatic : ICommand
        {
            Func<bool> _check;
            Action _invoke;

            public CommandOnStatic(Func<bool> check, Action invoke)
            {
                _check = check;
                _invoke = invoke;
            }

            /// <summary>
            /// We can only execute if we have something in our stack.
            /// </summary>
            /// <param name="parameter"></param>
            /// <returns></returns>
            public bool CanExecute(object parameter)
            {
                return _check();
            }

            public event EventHandler CanExecuteChanged;

            /// <summary>
            /// Simple guy to help with firing off the execute.
            /// </summary>
            public void FireExecuteChanges()
            {
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            }

            /// <summary>
            /// Go to whatever is on top of the stack.
            /// </summary>
            /// <param name="parameter"></param>
            public void Execute(object parameter)
            {
                _invoke();
            }
        }

        /// <summary>
        /// Create the commands
        /// </summary>
        private void SetupActions()
        {
            _moveForwardCommand = new CommandOnStatic(
                () =>
                {
                    return _locationList.Count > (_locationIndex + 1);
                },
                () =>
                {
                    _viewer.LoadCollection(_locationList[_locationIndex + 1]._uri.OriginalString, _locationList[_locationIndex + 1]._viewState);
                });
            _moveBackCommand = new CommandOnStatic(
                () =>
                {
                    return _locationIndex > 0;
                },
                () =>
                {
                    _viewer.LoadCollection(_locationList[_locationIndex - 1]._uri.OriginalString, _locationList[_locationIndex - 1]._viewState);
                });
        }

        /// <summary>
        /// If something in the stacks has changed, then this should be called here to make sure that
        /// the status gets updated.
        /// </summary>
        private void UpdateActionStatus()
        {
            _moveForwardCommand.FireExecuteChanges();
            _moveBackCommand.FireExecuteChanges();
        }


        #endregion
    }
}
